/*!
\copyright  Copyright (c) 2023 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Synchronised start for A2DP, kymera implementation.

*/

#include "../sync_start_a2dp_audio.h"
#include "../sync_start_a2dp_ctx.h"
#include "sync_start_a2dp.h"
#include "sync_start_a2dp_debug_pio.h"

#include <kymera.h>
#include <kymera_data.h>
#include <kymera_a2dp.h>
#include <kymera_a2dp_private.h>
#include <kymera_output.h>
#include <kymera_music_processing.h>
#include <kymera_latency_manager.h>
#include <kymera_state.h>
#include <handset_service.h>
#include <multidevice.h>
#include <av_seids.h>
#include <logging.h>
#include <vm.h>
#include "aptx_ad_utils.h"

/*! Helper defines for RTP header format. These
    are used for hash transform configuration */
#define RTP_HEADER_LENGTH (12)
#define RTP_HEADER_SEQUENCE_NO_OFFSET (2)

bool SyncStartA2dp_Init(Task init_task)
{
    UNUSED(init_task);

    return TRUE;
}

sync_start_a2dp_info_t *SyncStartA2dp_GetInfo(void)
{
#ifdef INCLUDE_MIRRORING
    kymeraTaskData *theKymera = KymeraGetTaskData();
    return &theKymera->sync_info;
#else
    return NULL;
#endif
}

bool SyncStartA2dp_IsA2dpMirrored(void)
{
    return Kymera_A2dpIsForwarding();
}

bool SyncStartA2dp_IsA2dpStreaming(void)
{
    return Kymera_A2dpIsStreaming();
}

void SyncStartA2dp_StartGraph(bool only_output)
{
    /* Start the output chain regardless of whether the source was connected
    to the input chain. Failing to do so would mean audio would be unable
    to play a tone. This would cause kymera to lock, since it would never
    receive a KYMERA_OP_MSG_ID_TONE_END and the kymera lock would never
    be cleared. */
    KymeraOutput_ChainStart();

    if(!only_output)
    {
        SyncStartA2dp_StartInputGraph();
    }
}

void SyncStartA2dp_StartInputGraph(void)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    ChainStart(theKymera->chain_input_handle);
    Kymera_StartMusicProcessingChain();
}

static void appKymeraConfigureAndStartPacketiserTransform(kymeraTaskData *theKymera, Source source)
{
#ifdef INCLUDE_MIRRORING
    Sink sink;
    Transform packetiser;
#ifdef ENABLE_TWM_STEREO
    bool set_qvcs_filter = FALSE;
#endif /* ENABLE_TWM_STEREO */
    sink = ChainGetInput(theKymera->chain_input_handle, EPR_SINK_MEDIA);
    bool cp_header_enabled = theKymera->cp_header_enabled;
    uint32 rate = KymeraOutput_GetMainSampleRate();

    DEBUG_LOG("appKymeraConfigureAndStartPacketiserTransform rate %d", rate);

    /* Stop and destroy packetiiser transform if already present */
    if (theKymera->hashu.packetiser)
    {
        DEBUG_LOG("appKymeraConfigureAndStartPacketiserTransform, destroy"
                  "already present packetiser transform:0x%x", theKymera->hashu.packetiser);
        TransformDisconnect(theKymera->hashu.packetiser);
        theKymera->hashu.packetiser = NULL;
    }

    /* Connect source with chain input via packetiser transform */
    packetiser = TransformPacketise(source, sink);

    if (packetiser)
    {
        int16 hq_latency_adjust;
        int16 aptx_glbl_latency_adjust;
        int16 ll_adjust = aptxAdaptiveTTPLatencyAdjustLL();
        uint32 ssrc_cfg;

        hq_latency_adjust = Kymera_LatencyManagerIsGamingModeEnabled() ?
                                aptxAdaptiveTTPLatencyAdjustHQGaming() :
                                aptxAdaptiveTTPLatencyAdjustHQStandard();

        aptx_glbl_latency_adjust = Kymera_LatencyManagerIsGamingModeEnabled() ?
                                aptxAdaptiveTTPLatencyAdjustGaming() :
                                aptxAdaptiveTTPLatencyAdjustStandard();

        /* if Multipoint is enabled, we need to adjust the LL latency */
        if (HandsetService_IsBrEdrMultipointEnabled())
            ll_adjust = aptxAdaptiveTTPLatencyMPAdjustLL();

#ifdef ENABLE_TWM_STEREO
        if(appKymera_IsSpeakerStereo(theKymera))
        {
            ll_adjust = HandsetService_IsBrEdrMultipointEnabled() ? aptxAdaptiveTTPLatencyMPAdjustLLForSpkStereo() : aptxAdaptiveTTPLatencyAdjustLLForSpkStereo();
            hq_latency_adjust = Kymera_LatencyManagerIsGamingModeEnabled() ?
                                  (rate == SAMPLE_RATE_96000)? aptxAdaptiveTTPLatencyAdjustHQStandardForSpkStereo() : aptxAdaptiveTTPLatencyAdjustHQGamingForSpkStereo() :
                                  aptxAdaptiveTTPLatencyAdjustHQStandardForSpkStereo();
        }
#endif /* ENABLE_TWM_STEREO */

        /* R2.2 stereo lossless decoder requires a different packetiser configuration */
#ifdef ENABLE_TWM_STEREO
        if (appKymeraIsAptxR22Enabled() && appKymera_IsSpeakerStereo(theKymera))
#else
        if (appConfigEnableAptxAdaptiveStereoMixLossless() && (theKymera->aptx_adaptive_r22_dec))
#endif /* ENABLE_TWM_STEREO */
            PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CODEC, VM_TRANSFORM_PACKETISE_CODEC_APTX_ADAPTIVE));
        else
            PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CODEC, VM_TRANSFORM_PACKETISE_CODEC_APTX));

        PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_MODE, VM_TRANSFORM_PACKETISE_MODE_TWSPLUS));
        PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_SAMPLE_RATE, (uint16) rate));
        PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_CPENABLE, (uint16) cp_header_enabled));
        PanicFalse(TransformConfigure(packetiser, VM_TRANSFORM_PACKETISE_TTP_DELAY, aptx_glbl_latency_adjust));

        theKymera->aptx_adaptive_ttp_ssrc = PanicUnlessMalloc(sizeof(vm_transform_ttp_ssrc_pair) * APTX_SSRC_COUNT);
        ssrc_cfg = (uint32)theKymera->aptx_adaptive_ttp_ssrc;
        theKymera->aptx_adaptive_ttp_ssrc[0] = (vm_transform_ttp_ssrc_pair){.ssrc = aptxAdaptiveLowLatencyStreamId_SSRC_Q2Q(), .ttp_adjust = ll_adjust};
        theKymera->aptx_adaptive_ttp_ssrc[1] = (vm_transform_ttp_ssrc_pair){.ssrc = aptxAdaptiveHQStreamId_SSRC(), .ttp_adjust = hq_latency_adjust};
        theKymera->aptx_adaptive_ttp_ssrc[2] = (vm_transform_ttp_ssrc_pair){.ssrc = aptxAdaptiveLosslessStreamId_SSRC(), .ttp_adjust = hq_latency_adjust};

        PanicFalse(TransformConfigure(packetiser,VM_TRANSFORM_PACKETISE_TTP_SSRC_PAIR_COUNT, APTX_SSRC_COUNT));
        PanicFalse(TransformConfigure(packetiser,VM_TRANSFORM_PACKETISE_TTP_SSRC_PAIR_ADDR_HI, (uint16)((ssrc_cfg & 0xffff0000UL) >>16)));
        PanicFalse(TransformConfigure(packetiser,VM_TRANSFORM_PACKETISE_TTP_SSRC_PAIR_ADDR_LO, (uint16)(ssrc_cfg & 0x0000ffff)));

        if (!TransformStart(packetiser))
        {
            DEBUG_LOG_WARN("appKymeraConfigureAndStartPacketiserTransform, failed to start transform");
            TransformDisconnect(packetiser);
            StreamConnectDispose(source);
            return;
        }

        theKymera->hashu.packetiser = packetiser;

#ifdef ENABLE_TWM_STEREO
        if (appKymera_IsStereoMirroring(theKymera))
        {
            /* Call to configure the apps0 Transform for split tx */
            appKymeraSetAptxADMixerModes(INVALID_OPERATOR, appKymera_IsStereoMirroringLeft(theKymera), FALSE);
            if (theKymera->split_tx_mode)
            {
                /* Set flag to configure QVCS filters for the BT */
                set_qvcs_filter = TRUE;
            }
        }
#else
        /* Call to configure the apps0 Transform for split tx */
        appKymeraSetAptxADMixerModes(INVALID_OPERATOR, Multidevice_IsLeft(), FALSE);
#endif /* ENABLE_TWM_STEREO */

    }
    else
    {
        /* It is possible that the source may have been removed, so the call to create the packetiser will fail,
            connet the source to the Dispose transform and continue with setup. Stream will be shutdown by the dispose transform */
        DEBUG_LOG_WARN("appKymeraConfigureAndStartPacketiserTransform, failed to create transform");
        StreamConnectDispose(source);
    }

#ifdef ENABLE_TWM_STEREO
    if(appKymera_IsStereoMirroring(theKymera))
    {
        if (set_qvcs_filter)
        {
            AptxAdUtils_SetL2capFilter(theKymera->sink);
        }
        else
        {
            AptxAdUtils_DisableL2capFilter(theKymera->sink);
        }
    }
#endif /* ENABLE_TWM_STEREO */

#else
    UNUSED(theKymera);
    UNUSED(source);
#endif
}

static void appKymeraConfigureAndStartHashTransform(kymeraTaskData *theKymera, uint8 seid, Source source)
{
#ifdef INCLUDE_MIRRORING
    Sink chain_input = ChainGetInput(theKymera->chain_input_handle, EPR_SINK_MEDIA);
    bool cp_header_enabled = theKymera->cp_header_enabled;

    DEBUG_LOG("appKymeraConfigureAndStartHashTransform");

    /* Stop and destroy hash transform if already present */
    if (theKymera->hashu.hash)
    {
        DEBUG_LOG("appKymeraConfigureAndStartHashTransform, destroy"
                  "already present hash transform:0x%x", theKymera->hashu.hash);
        TransformDisconnect(theKymera->hashu.hash);
        theKymera->hashu.hash = NULL;
    }

    /* Connect source with chain input via hash transform */
    theKymera->hashu.hash = TransformHash(source, chain_input);

    if (theKymera->hashu.hash)
    {
        /* Configure the hash transform based on the codec type */
        switch (seid)
        {
            /* SBC and AAC codec packets have RTP header by default. No need to
            * prefix the header in hash transform */
            case AV_SEID_SBC_SNK:
            case AV_SEID_AAC_SNK:
#ifdef INCLUDE_APTX_ADAPTIVE
            case AV_SEID_APTX_ADAPTIVE_SNK: /* Non-Q2Q mode uses RTP */
#endif
#ifdef ENABLE_LDAC_SINK
            case AV_SEID_LDAC_SNK:
#endif
            {
                (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_PREFIX_RTP_HEADER, 0);
                /* Set source size to 0xFFFF to calculate hash for complete packet */
                (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_SOURCE_SIZE, 0xFFFF);
                (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_SOURCE_OFFSET, 0);
                (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_SOURCE_MODIFY_OFFSET, RTP_HEADER_SEQUENCE_NO_OFFSET);
            }
            break;

            /* aptX codec packet only has RTP header if content protection is enabled. If conent protection
             * is not enabled then configure hash transform to prefix the header to codec data */
            case AV_SEID_APTX_SNK:
            {
                if (cp_header_enabled)
                {
                    (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_PREFIX_RTP_HEADER, 0);
                    (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_SOURCE_MODIFY_OFFSET, RTP_HEADER_SEQUENCE_NO_OFFSET);
                }
                else
                {
                    (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_PREFIX_RTP_HEADER, 1);
                    /* payload type and SSRC aren't needed as such. Setting them to 0 for sanity */
                    (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_RTP_PAYLOAD_TYPE, 0);
                    (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_RTP_SSRC_LOWER, 0);
                    (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_RTP_SSRC_UPPER, 0);
                }
                /* Set source size to 0xFFFF to calculate hash for complete packet */
                (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_SOURCE_SIZE, 0xFFFF);
                (void) TransformConfigure(theKymera->hashu.hash, VM_TRANSFORM_HASH_SOURCE_OFFSET, 0);
            }
            break;
        }
        if (!TransformStart(theKymera->hashu.hash))
        {
            DEBUG_LOG("appKymeraConfigureAndStartHashTransform, failed to start transform");
            TransformDisconnect(theKymera->hashu.hash);
            StreamConnectDispose(source);
        }
    }
    else
    {
        DEBUG_LOG("appKymeraConfigureAndStartHashTransform, failed to create hash transform");
        /* This typically occurs when the source is destroyed before media is started.
           Tidy up by disposing */
        StreamConnectDispose(source);
    }
#else
    UNUSED(theKymera);
    UNUSED(seid);
    UNUSED(source);
#endif
}

void SyncStartA2dp_MessageMoreDataCreateTransform(Source source)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    if (theKymera->q2q_mode)
    {
        appKymeraConfigureAndStartPacketiserTransform(theKymera, source);
    }
    else
    {
        appKymeraConfigureAndStartHashTransform(theKymera, theKymera->a2dp_seid, source);
    }
}

void SyncStartA2dp_SetRtpTtpMode(ttp_mode_t mode, uint32 *sync_time_instant)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    Operator op_rtp = ChainGetOperatorByRole(theKymera->chain_input_handle, OPR_RTP_DECODER);
    uint32 latency = Kymera_LatencyManagerGetLatencyForSeidInUs(theKymera->a2dp_seid);
    rtime_t ttp_us = 0;

    if(sync_time_instant)
    {
        ttp_us = rtime_add(*sync_time_instant, latency);
    }

    if(op_rtp != INVALID_OPERATOR)
    {
        OperatorsStandardSetTtpState(op_rtp, mode, ttp_us, 0, latency);
    }
}


void SyncStartA2dp_LogStates(void)
{
#ifdef INCLUDE_MIRRORING
    kymeraTaskData *theKymera = KymeraGetTaskData();
    DEBUG_LOG("appKymeraA2dpHandleAudioSyncStreamInd: enum:kymera_a2dp_state_t:%d, enum:appKymeraState:%d, enum:mirror_profile_a2dp_start_mode_t:%d",
                  appKymeraA2dpGetState(), appKymeraGetState(), theKymera->sync_info.mode);
#endif
}

void SyncStartA2dp_LogStartTime(uint32 clock)
{
    rtime_t ttp_us;
    uint32 latency;
    kymeraTaskData *theKymera = KymeraGetTaskData();

    latency = Kymera_LatencyManagerGetLatencyForSeidInUs(theKymera->a2dp_seid);
    ttp_us = rtime_add(clock, latency);

    DEBUG_LOG("appKymeraA2dpSetSyncStartTime, clock:0x%x, current_us:0x%x, ttp_us:0x%x",
                clock, VmGetTimerTime(), ttp_us);
}

/*! Enable toggling on PIO21 during key A2DP mirroring start events.
    This is useful for determining the time taken in the different
    parts of the start procedure.

    The PIOs need to be setup in pydbg as outputs controlled by P1:
    mask = 1<<21
    apps1.fw.call.PioSetMapPins32Bank(0, mask, mask)
    apps1.fw.call.PioSetDir32Bank(0, mask, mask)
*/
#ifdef KYMERA_PIO_TOGGLE
#include "pio.h"
#define KYMERA_PIO_MASK (1<<21)
#endif

void SyncStartA2dp_PioSet(void)
{
#ifdef KYMERA_PIO_TOGGLE
    PioSet32Bank(0, KYMERA_PIO_MASK, KYMERA_PIO_MASK);
#endif
}

void SyncStartA2dp_PioClr(void)
{
#ifdef KYMERA_PIO_TOGGLE
    PioSet32Bank(0, KYMERA_PIO_MASK, 0);
#endif
}

const uint16 * SyncStartA2dp_GetA2dpNotPreparedLock(void)
{
    return NULL;
}

void SyncStartA2dp_ConfigureAudioSyncLatency(Source source)
{
    /* Setting STREAM_AUDIO_SYNC_LATENCY in not yet supported on this platform*/
    UNUSED(source);
}
